feat(dashboard): add read-only fleet status HTTP dashboard#410
Conversation
Adds `strut dashboard [--port N] [--bind addr] [--json]`, a zero-dependency (socat) HTTP server exposing fleet + stack health as an auto-refreshing HTML page or JSON, so operators can check "is my fleet healthy" without SSHing into each host. Binds to 127.0.0.1 by default. Reuses `fleet status --json` and `status-all --json`, with per-stack drift aggregated fresh at /api/drift, and caches each 30s so rapid refreshes don't re-run SSH probes.
There was a problem hiding this comment.
🔎 Agent review (sonnet→opus) — CONCERNS
REVIEW: CONCERNS
RESOLVES: full
The command faithfully implements the plan (socat handler, 30s file cache, /api/fleet|stacks|drift, HTML render with jq degradation, localhost bind, good unit tests) and meets the JSON/HTML acceptance criteria. Two real concerns remain: the temp cache dir leaks on Ctrl+C, and every HTTP response depends on bash-only source/pipefail that socat's /bin/sh handler may not support.
2 concerns · 1 nit — 3 inline on the diff
gfargo
left a comment
There was a problem hiding this comment.
Address the concerns and NITs please
Addresses PR review feedback on #410: - Register INT/TERM traps that remove the cache dir before exiting, since socat blocks in the foreground and the EXIT-only cleanup chain never fires on Ctrl+C. - Run the per-connection handler via socat's EXEC (fork+exec) instead of SYSTEM (fork+`/bin/sh -c`), writing it to an executable file with a bash shebang so it isn't at the mercy of the host's /bin/sh (dash on Debian/Ubuntu silently breaks the handler's source/pipefail use). - Show actual cache freshness ("12s ago") in the HTML "Last refresh" line instead of the page-render timestamp, and document why the host table uses a Branch column rather than the mock's Last Deploy (fleet status has no per-host deploy timestamp).
There was a problem hiding this comment.
🔎 Agent re-review (sonnet, delta) — LGTM
REVIEW: LGTM
RESOLVES: full
The revise correctly fixes both prior issues (SIGINT/SIGTERM cache cleanup via INT/TERM traps mirroring the existing cmd_group.sh pattern, and EXEC+bash-shebang handler to avoid dash /bin/sh) and adds matching test coverage; no regressions or scope creep found.
1 nit — 1 inline on the diff
| @test "_dashboard_cache_age: reports seconds since the cache file was written" { | ||
| export _DASH_CACHE_DIR="$BATS_TEST_TMPDIR" | ||
| echo '{}' > "$BATS_TEST_TMPDIR/agekey.json" | ||
| touch -d "@$(($(date +%s) - 15))" "$BATS_TEST_TMPDIR/agekey.json" |
There was a problem hiding this comment.
🧹 New test uses GNU-only touch -d
The new _dashboard_cache_age test sets an mtime with touch -d "@$(...)", which is GNU-coreutils-only syntax. Every other mtime-manipulating test in this suite (test_backup_helpers.bats, test_backup_retention.bats, test_status_all.bats) uses a date -v/date -d fallback chain for BSD/macOS portability, and _dashboard_file_mtime itself supports both stat -c and stat -f. This one test would fail on a macOS test runner.
What
Adds a
strut dashboardcommand that starts a zero-dependency (socat) HTTP server exposing fleet + stack health as an auto-refreshing HTML page and JSON API endpoints — answering "is my fleet healthy right now?" without SSHing into each box.Why
Closes #335
Plane: OSS-751
How
lib/cmd_dashboard.sh:cmd_dashboard(arg parsing, socat guard, cache-dir setup) + a per-connection heredoc handler (mirrorslib/cmd_webhook.sh's serve pattern) routing/,/api/fleet,/api/stacks,/api/drift/api/fleet→strut fleet status --json;/api/stacks→strut status-all --json;/api/drift→ new per-stack aggregation (no existing fleet-wide drift aggregator)forkspawns one process per connection, so in-memory caching isn't possible)_dashboard_render_htmlrenders the HTML table (host status/behind/dirty + per-stack health), degrading to a<pre>JSON dump whenjqis unavailable127.0.0.1by default (--bindto override);--jsonmakesGET /return JSON instead of HTMLstrutentrypoint (lazy-sourced, likestatus-all), shell completions, and READMETesting
bats tests/test_cmd_dashboard.bats— 19/19 passing (arg parsing, socat guard, cache TTL behavior, drift aggregation, HTML rendering incl. HTML-escaping)bats tests/— full suite passes; the ~55 pre-existing failures (audit/backup/keys/rollback JSON tests) reproduce identically onmainin this sandbox (missingjq) and are unrelated to this changeshellcheckunavailable in this sandbox — please verify in CI)Closes #335